Skip to content

fix: persist Claude crew authentication across restarts - #34

Open
ruby-dlee wants to merge 13 commits into
mainfrom
fm/claude-crew-auth-persist-z3
Open

fix: persist Claude crew authentication across restarts#34
ruby-dlee wants to merge 13 commits into
mainfrom
fm/claude-crew-auth-persist-z3

Conversation

@ruby-dlee

@ruby-dlee ruby-dlee commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

Claude direct-account launches now use one authoritative native-credential check and the same upgrade-stable Claude launcher for selection, spawn preflight, and the launched process.
The authentication implementation is committed as 97e3aaa on fm/claude-crew-auth-persist-z3; the synchronized no-mistakes head is ff91b956 and the open pull request is #34.
This branch is intentionally stacked on sibling guardrail commit 126330f, because that change supplies the spawn preflight which consumes this branch's shared account-usability check.
The sibling fm-guardrail-hardening-g6 change must land first, after which this branch must be rebased; the stack was not unpicked or rewritten.
Unauthenticated homes fail before endpoint creation instead of consuming a worktree lease at a login prompt.

What changed

bin/fm-account-directory.sh now owns the verified macOS Keychain mechanism, one-time setup and repair commands, claude setup-token tradeoff, and the authoritative check-credential claude <account-home> contract.
The check clears ambient Anthropic and Claude credentials, sets the literal CLAUDE_CONFIG_DIR, invokes the passwd-home ~/.local/bin/claude symlink, and requires auth status --json to report loggedIn=true.
Claude selection skips homes that fail that same check and chooses the stable first usable home.
bin/fm-spawn.sh launches the exact ~/.local/bin/claude path returned by the owner script instead of a PATH-selected wrapper or a stale versioned binary.
The sibling preflight remains the only spawn guard and calls the shared check; this change did not add a competing preflight.

Investigation disproved the proposed application-ACL cause.
All three account services mapped to the expected path hashes and had the reported modification times.
security find-generic-password -w succeeded without prompting for all three broken items and the working temporary item, with secret output discarded.
ACL dumps showed the same /usr/bin/security decrypt path for broken and working items.
The distinguishing Keychain metadata was the account attribute: the broken imported homes retained the passwd username while the newly authenticated working custom home used unknown.
Passing each broken item's access token only through a transient CLAUDE_CODE_OAUTH_TOKEN environment made Claude report authenticated, proving the payload was usable while native discovery was not.
The selected repair is therefore a clean, one-time native claude auth login after removing only that literal config directory's stale service entry.

claude setup-token was verified as an honored CLAUDE_CODE_OAUTH_TOKEN source.
It was not selected because Claude prints but does not store that one-year inference-only token, which would force Firstmate to add a second credential store and propagate a secret through the launch path.
Native login keeps secret storage and refresh inside Claude's own per-directory Keychain contract.

Verification

tests/fm-account-directory.test.sh passed in full, including authenticated selection, all-unusable refusal, ambient credential clearing, stale version-manifest rejection, exact-launcher spawn, pre-endpoint unauthenticated refusal, and the existing direct-account recovery and rollback coverage.
FM_TEST_FOCUSED=credential-preflight tests/fm-account-directory.test.sh passed.
bin/fm-lint.sh passed with pinned ShellCheck 0.11.0.
bash -n passed for both changed scripts and the changed test.
git diff --check passed.
/Users/dongkeun/firstmate/bin/fm-ensure-agents-md.sh . confirmed the existing AGENTS.md and CLAUDE.md arrangement was unchanged.

The unrelated pre-existing failure in tests/fm-watch-checkpoint.test.sh was accepted only after direct base/head proof.
Running the unmodified file at submitted base fe94d84b515f0a1b28d8ad988714dbd5b521ebd6 exited 1 with signal checkpoint exit: expected exit 0, got 124.
Running the same unmodified file at branch head 97e3aaa2121b07424a44e6a9a73a9d81a1ffc50d produced the identical assertion and exit code.
This branch does not modify that file; no retry, timeout relaxation, or test weakening was added.
No-mistakes recorded the failure as accepted pre-existing evidence and continued, while the authentication suites and all 449 Agent Fleet Python tests passed.

Real-machine use confirmed provider-command claude returns /Users/dongkeun/.local/bin/claude.
The authoritative check correctly fails all three existing account homes and prints their exact scoped login commands.
The same stable launcher reports loggedIn=true for the demonstrably working /tmp/claude-alt-qfS707 throwaway custom config directory.
A second purpose-made authenticated directory could not be created unattended because OAuth authorization is the one intentional human setup step.
A real Herdr crew was not started because this brief explicitly declared Herdr lifecycle operations not enabled.
Per supervisor decision, neither limitation blocks shipping; end-to-end real-crew verification is explicitly outstanding.

Visual evidence

No visual artifact would add review value for this shell and Keychain mechanism.
No screenshots were captured.

Artifacts

The authentication implementation commit is 97e3aaa; the synchronized pipeline head is ff91b956 on branch fm/claude-crew-auth-persist-z3.
The pull request is #34.
The branch intentionally contains sibling prerequisite 126330f and must be rebased after that prerequisite lands.
The important tracked files are bin/fm-account-directory.sh, bin/fm-spawn.sh, and tests/fm-account-directory.test.sh.
The authoritative check is bin/fm-account-directory.sh check-credential claude <account-home>.
Official Claude documentation used during evaluation was https://code.claude.com/docs/en/team and https://code.claude.com/docs/en/env-vars.

Follow-ups

End-to-end verification against a real Claude crew launch is outstanding by explicit supervisor decision.
The account owner runs this exact one-time setup sequence when available:

cd /Users/dongkeun/firstmate
for account_home in \
  /Users/dongkeun/.local/share/agent-fleet/accounts/claude/1 \
  /Users/dongkeun/.local/share/agent-fleet/accounts/claude/2 \
  /Users/dongkeun/.local/share/agent-fleet/accounts/claude/3
do
  suffix="$(printf '%s' "$account_home" | shasum -a 256 | cut -c1-8)"
  service="Claude Code-credentials-$suffix"
  while security find-generic-password -s "$service" >/dev/null 2>&1; do
    security delete-generic-password -s "$service" >/dev/null || break
  done
  CLAUDE_CONFIG_DIR="$account_home" "$HOME/.local/bin/claude" auth login
  bin/fm-account-directory.sh check-credential claude "$account_home"
done

That initial OAuth step needs the account owner, but subsequent spawns, Firstmate restarts, and Claude Code version upgrades use the persistent Keychain credential without another manual login.
After at least one home passes the authoritative check, run this exact Herdr-enabled smoke sequence from the primary Firstmate home:

cd /Users/dongkeun/firstmate
export FM_HOME=/Users/dongkeun/firstmate-home
smoke_id=claude-auth-smoke-v1
bin/fm-brief.sh "$smoke_id" firstmate --scout --herdr-lab
perl -0pi -e "s/\\{TASK\\}/Start normally, read this brief, write data\\/$smoke_id\\/report.md stating that authenticated Claude startup succeeded, append done status, and stop./g" \
  "$FM_HOME/data/$smoke_id/brief.md"
bin/fm-spawn.sh "$smoke_id" /Users/dongkeun/firstmate \
  --harness claude --backend herdr --account-pool claude-smoke --scout
bin/fm-peek.sh "$smoke_id"

The proof is that the pane shows the crewmate consumed the brief instead of Not logged in - Please run /login, and the task produces its scout report plus done: status.
Firstmate should then follow the ordinary scout review and safe teardown lifecycle.

Merge ordering remains outstanding: land fm-guardrail-hardening-g6 first, then rebase this stacked branch before merge.
Do not rewrite the stack into an independent change, because the shared check-credential contract and the sibling preflight were designed together.

Pipeline

Updates from git push no-mistakes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant